//
// Created by luo on 2022-08-31.
//
#include "../opencv/OpenCv.h"

/*
 * 图像直方图是对图像像素的统计，统计图像中每个灰度值的个数，灰度值为x轴，个数为y轴绘图或者比例图
 * 映射灰度集中区域到稀疏区域，使得灰度值分布更符合期望状态
 * 分析图像亮暗对比度，并调整
 * */

void QuickDemo::my_calhist(Mat &mat)//绘制图像直方图
{
    /*
     * 统计图像中每个灰度值个数，但直方图绘制需自己进行
     * void calcHist(images,nimages,channels,mask,hist,dims,histSize,ranges,uniform,accumulate)
     * images 待统计直方图的图像数组，CV_8/16/32U类型，nimages 输入的图像数量
     * channels 需要统计的通道索引数组，第一个图像通道从0到images[0].channels()-1,第二个图像从images[0].channels()到images[1].channels()-1，以此类推
     * mask 可选的操作掩码，若是空矩阵，图片所有位置像素都会被统计，不为空，必须输入图像(尺寸相同数据类型为8U)
     * hist 输出的统计直方图结果，dims维度数组
     * dims 需要计算直方图的维度，必须是整数(1)
     * histSize 存放每个维度直方图的数组的尺寸(像素最大值)
     * ranges 每个图像通道中灰度值的取值范围(二次指针)
     * uniform 直方图是否均匀标志(默认为true) accumulate 是否累计统计直方图标志，若是，之前的图像统计结果不会被清除
     * */

    /*
     * 首先使用calcHist()统计灰度图像中每个灰度值数目，然后通过不断绘制矩形(类似微积分逼近思想)实现直方图绘制
     * 部分灰度值像素数目多，因而将每个灰度值数目缩小为原来1/20，使用了cvRound()四舍五入取整函数
     * */
    Mat gray;
    cvtColor(mat,gray,COLOR_BGR2GRAY);
    Mat hist; //设置提取直方图的相关变量
    const int channels[1] = {0}; //通道索引
    const int bins[1] = {256}; //直方图的维度，像素最大值
    float inRanges[2] = {0,255};
    const float * ranges[1] = {inRanges}; //像素灰度值的范围
    calcHist(&gray,1,channels,Mat(),hist,1,bins,ranges); //计算图像直方图
    //准备绘制直方图
    int hist_w = 512;
    int hist_h = 400;
    int width = 2;
    Mat histImage = Mat::zeros(hist_h,hist_w,CV_8UC3);
    for(auto i = 1; i <= hist.rows; i++)//hist是输出的统计直方图结果
    {
        rectangle(histImage,Point(width*(i-1),hist_h-1),
                  Point(width*i-1,hist_h-cvRound(hist.at<float>(i-1)/15)),
                  Scalar(255,255,255),-1);
    }
    namedWindow("histImage",WINDOW_AUTOSIZE);
    imshow("histImage",histImage);
    imshow("gray",gray);
    waitKey(0);
}

/*
 * 直方图归一化，由于通过求每个灰度值在所有像素数目的比例来表示某个灰度值数目多少
 * 导致在CV_8U图像中平均每个像素灰度值比例非常低，因此需要将比例扩大一定倍数后再绘制图像
 * 还有一种归一化就是寻找结果最大值，把所有结果除以这个最大值，实现数据归一化为0-1
 * */
void QuickDemo::my_normalizes(Mat &mat)//数据归一化
{
    /*
     * void normalize(src,dst,alpha,beta,norm_type,dtype,mask)
     * 输入数组矩阵，输出矩阵，alpha 范围归一化下到下限边界标准值，beta 上限范围
     * norm_type 归一化过程数据范数种类标志，输出数据类型选择标志(<0则与src相同，>=0则与src有相同通道但数据类型不同)
     * NORM_INF 无穷范数，向量最大值
     * */
    system("color F0");
    vector<double> positiveData = {2.0,8.0,10.0};
    vector<double>normalized_L1,normalized_L2,normalized_Inf,normalized_L2SQR;
    //测试不同归一化方法
    normalize(positiveData,normalized_L1,1.0,0.0,NORM_L1);//绝对值求和归一化
    cout<<"normalized_L1=["<<normalized_L1[0]<<" "<<normalized_L1[1]<<" "
    <<normalized_L1[2]<<"]"<<endl;
    normalize(positiveData,normalized_L2,1.0,0.0,NORM_L2);//模长归一化
    cout<<"normalized_L2=["<<normalized_L2[0]<<" "<<normalized_L2[1]<<" "
        <<normalized_L2[2]<<"]"<<endl;
    normalize(positiveData,normalized_Inf,1.0,0.0,NORM_INF);//最大值归一化
    cout<<"normalized_INF=["<<normalized_Inf[0]<<" "<<normalized_Inf[1]<<" "
        <<normalized_Inf[2]<<"]"<<endl;
    normalize(positiveData,normalized_L2SQR,1.0,0.0,NORM_MINMAX);//偏移归一化
    cout<<"normalized_L2SQR=["<<normalized_L2SQR[0]<<" "<<normalized_L2SQR[1]<<" "
        <<normalized_L2SQR[2]<<"]"<<endl;

    //将图像直方图归一化
    Mat gray,hist;
    cvtColor(mat,gray,COLOR_BGR2GRAY);
    const int channels[1] = {0};
    float inRanges[2] = {0,255};
    const float* ranges[1] = {inRanges};
    const int bins[1] = {256};
    calcHist(&gray,1,channels,Mat(),hist,1,bins,ranges);
    //准备绘制直方图
    int hist_w = 512;
    int hist_h = 400;
    int width = 2;
    Mat histImage_L1 = Mat::zeros(hist_h,hist_w,CV_8UC3);
    Mat histImage_Inf = Mat::zeros(hist_h,hist_w,CV_8UC3);
    Mat hist_L1,hist_Inf;
    normalize(hist,hist_L1,1,0,NORM_L1,-1,Mat());
    for(auto i = 1; i <= hist_L1.rows; i++)//hist是输出的统计直方图结果
    {
        rectangle(histImage_L1,Point(width*(i-1),hist_h-1),
                  Point(width*i-1,hist_h-cvRound(30*hist_h*hist_L1.at<float>(i-1))-1),
                  Scalar(255,255,255),-1);
    }

    normalize(hist,hist_Inf,1,0,NORM_INF,-1,Mat());
    for(auto i = 1; i <= hist_Inf.rows; i++)//hist是输出的统计直方图结果
    {
        rectangle(histImage_Inf,Point(width*(i-1),hist_h-1),
                  Point(width*i-1,hist_h-cvRound(hist_h*hist_Inf.at<float>(i-1))-1),
                  Scalar(255,255,255),-1);
    }
    imshow("histImage_L1",histImage_L1);
    imshow("histImage_Inf",histImage_Inf);
    waitKey(0);

}

//绘制直方图函数
void drawHist(Mat &hist,int type,string name)//归一化并绘制直方图函数
{
    int hist_w = 512;
    int hist_h = 400;
    int width = 2;
    Mat histImage = Mat::zeros(hist_h,hist_w,CV_8UC3);
    normalize(hist,hist,255,0,type,-1,Mat());
//    for(int i = 1; i<= hist.rows; i++)
//    {
//        rectangle(histImage,Point(width*(i-1),hist_h-1),
//                  Point(width*i-1,hist_h-cvRound(20*hist_h*hist.at<float>(i-1))-1),
//                        Scalar(255,255,255),-1);
//    }
    namedWindow("name",WINDOW_NORMAL);
    imshow(name,histImage);
}

//输出直方图数组
void OutputHist(Mat &mat)
{
    Mat gray,hist,hist2;
    cvtColor(mat,gray,COLOR_BGR2GRAY);
    Mat equalImag;
    equalizeHist(gray,equalImag); //将图像直方图均衡化
    const int channels[1] = {0};
    float inRanges[2] = {0,255};
    const float* ranges[1] = {inRanges};
    const int bins[1] = {256};
    calcHist(&gray,1,channels,Mat(),hist,1,bins,ranges);
    calcHist(&equalImag,1,channels,Mat(),hist2,1,bins,ranges);
    drawHist(hist,NORM_INF,"hist");
    drawHist(hist,NORM_L1,"hist");
    drawHist(hist2,NORM_INF,"hist2");
    imshow("origin picture",gray);
    imshow("after equal",equalImag);
    waitKey(0);
}

/*
 * 直方图比较，通过图像像素灰度值比较两幅图像的相似程度
 * */
void QuickDemo::my_comparehist(Mat &mat)//直方图比较
{
    /*
     * double compareHist(H1,H2,method);
     * 第一幅图像直方图H1，第二幅图像直方图H2，比较方法标志
     * HISTCMP_CORREL 相关法 两直方图完全一致数值为1，完全不相关为0
     * HISTCMP_CHISQR 卡方法 两直方图完全一致数值为0，两图相似性越小，计算数值越大
     * */
    system("color F0");
    Mat gray,hist,gray2,hist2,gray3,hist3;
    cvtColor(mat,gray,COLOR_BGR2GRAY);
    resize(gray,gray2,Size(),0.5,0.5);
    gray3 = imread("C:\\Pictures\\16.jpg",IMREAD_GRAYSCALE);
    const int channels[1] = {0};
    float inRanges[2] = {0,255};
    const float* ranges[1] = {inRanges};
    const int bins[1] = {256};
    calcHist(&gray,1,channels,Mat(),hist,1,bins,ranges);
    calcHist(&gray2,1,channels,Mat(),hist2,1,bins,ranges);
    calcHist(&gray3,1,channels,Mat(),hist3,1,bins,ranges);
    drawHist(hist,NORM_INF,"hist");
    drawHist(hist2,NORM_INF,"hist2");
    drawHist(hist3,NORM_INF,"hist3");
    //原直方图与原直方图的相关系数
    double hist_hist = compareHist(hist,hist,HISTCMP_CORREL);
    cout<<"hist:"<<hist_hist<<endl;
    //原直方图与缩小原图后直方图的相关系数
    double hist_hist2 = compareHist(hist,hist2,HISTCMP_CORREL);
    cout<<"hist:"<<hist_hist2<<endl;
    //两幅不同图像的直方图相关系数
    double hist_hist3 = compareHist(hist,hist3,HISTCMP_CORREL);
    cout<<"hist:"<<hist_hist3<<endl;
    waitKey(0);

}

/*
 * 直方图应用，增强图像对比度，在图像中寻找相似区域等
 * 调整直方图分布提高图像对比度，反向投影寻找相同区域，将图像的对比度调整为指定的形式
 * */
void QuickDemo::my_equalizehist(Mat &mat)//图像直方图均衡化
{
    /*
     * 直方图均衡化，通过映射，将图像灰度值范围扩大，
     * 增加原来两个灰度值之间的差值，提高图像对比度，进而将图像中的纹理显现。
     * void equalizeHist(src,dst);
     * 需要直方图均衡化的CV_8UC1图像src，直方图均衡化后的输出图像dst
     * 该函数只能对单通道灰度图操作
     * */
    OutputHist(mat);
}

/*
 * 图像直方图匹配，将直方图映射成指定的分布形式，有目的地增强某个灰度区间
 * 首先要知道变换后的灰度直方图分布形式，进而确定变换函数
 * 累计概率为小于或等于某一灰度值的像素数目占所有像素的比例
 * n,r = arg min(n,r)|Vs(n) - Vz(r)|
 * 原直方图各个灰度值累积概率-匹配后直方图各个灰度级累积概率 求取最小值

 * */
void QuickDemo::my_histmatch(Mat &mat)//图像直方图匹配
{
    /*
     * 在代码中构建寻找灰度值匹配的过程，构建原直方图累计概率与目标直方图累计概率之间的差值表
     * 需要自己实现算法实现图像直方图匹配
     * */
    Mat mat2 = imread("C:\\Pictures\\16.jpg");
    if (mat2.empty()) {
        cout << "Error" << endl;
        return ;
    }
    Mat gray1,gray2,hist1,hist2;
    cvtColor(mat,gray1,COLOR_BGR2GRAY);
    cvtColor(mat2,gray2,COLOR_BGR2GRAY);
    //Mat equalImag;
    //equalizeHist(gray,equalImag); //将图像直方图均衡化
    const int channels[1] = {0};
    float inRanges[2] = {0,255};
    const float* ranges[1] = {inRanges};
    const int bins[1] = {256};
    calcHist(&gray1,1,channels,Mat(),hist1,1,bins,ranges);
    calcHist(&gray2,1,channels,Mat(),hist2,1,bins,ranges);
    drawHist(hist1,NORM_L1,"hist1");
    drawHist(hist2,NORM_L1,"hist2");
    //计算两幅图像直方图的累积概率
    float hist1_cdf[256] = {hist1.at<float>(0)};
    float hist2_cdf[256] = {hist2.at<float>(0)};
    for(size_t i = 1; i < 256; i++)
    {
        hist1_cdf[i] = hist1_cdf[i-1] + hist1.at<float>(i); // 计算<= 某灰度值的累积概率
        hist2_cdf[i] = hist2_cdf[i-1] + hist2.at<float>(i);
    }
    //构建累积概率误差矩阵
    float diff_cdf[256][256];
    for(auto i = 0; i < 256; i++)
    {
        for(auto j = 0; j < 256; j++)
        {
            //此处只是求绝对值，并未找最小
            diff_cdf[i][j] = fabs(hist1_cdf[i]-hist2_cdf[j]); //n,r = arg min(n,r)|Vs(n) - Vz(r)|
        }
    }
    //生成LUT
    Mat lut(1,256,CV_8U);//像素灰度值映射表，以灰度值为索引，以灰度值映射后的数值作为表内容
    for(int i = 0; i < 256; i++)
    {
        //查找源灰度级为i的映射灰度
        //与i的累积概率差值最小的规定化灰度
        float min = diff_cdf[i][0];
        int index = 0;
        for(auto j = 1; j <256; j++)
        {
            if(min > diff_cdf[i][j])
            {
                min = diff_cdf[i][j];
                index = j;
            }
        }
        lut.at<uchar>(i) = (uchar)index;
    }
    Mat result,hist3;
    LUT(mat,lut,result);
    imshow("before match img",mat);
    imshow(" match img",mat2);
    imshow("match result",result);
    calcHist(&result,1,channels,Mat(),hist3,1,bins,ranges);
    drawHist(hist3,NORM_L1,"hist3"); //绘制匹配后的图像直方图
    waitKey(0);

}

/*
 * 区域直方图可以看做结构或者形状的概率函数
 * 反向投影是记录给定图像中的像素点如何适应直方图模型像素分布方式的一种方法
 * 简言之，反向投影首先计算某一特征的直方图模型，然后用该模型去寻找图像中是否存在该特征
 * */
void QuickDemo::my_calbackproject(Mat &mat)//图像直方图反向投影
{
    /*
     * void calcBackProject(images,nimages,channels,hist,backProject,ranges,scale,uniform);
     * images 待统计直方图的图像数组，CV_8/16/32U类型，nimages 输入的图像数量
     * channels 需要统计的通道索引数组，第一个图像通道从0到images[0].channels()-1,第二个图像从images[0].channels()到images[1].channels()-1，以此类推
     * hist 输入统计直方图，dims维度数组
     * backProject 目标为反向投影图像，与images[0]具有相同尺寸和数据类型的单通道图像
     * ranges 每个图像通道中灰度值的取值范围(二次指针)
     * scale 输出反向投影矩阵的比例因子
     * uniform 直方图是否均匀的标志符
     * */

}

/*
 * 图像的模板匹配，因直方图不能直接反映图像纹理，
 * 如果两个不同模板图有相同直方图分布特性，则反向投影无意义
 * 因而，可以通过比较像素灰度值的形式来搜索是否存在相同的内容，称为模板匹配
 * 在待匹配图像中选择与模板图相同的内容
 * */

/*
 * 步骤:1、在待匹配图像中选取与模板尺寸大小相同的滑动窗口
 *     2、比较滑动窗口中每个像素与模板中对应像素灰度值的关系，计算模板与滑动窗口的相似性
 *     3、将滑动窗口从左上角->向右->最右边->向下一行->从最左侧重新开始，记录每一次移动后计算的模板与滑动窗口相似性
 *     4、比较所有位置的相似性，选择相似性最大的滑动窗口作为备选匹配结果
 * */
void QuickDemo::my_matchtemple(Mat &mat)//图像的模板匹配
{
    /*
     * void matchTemplate(image,templ,result,method,mask);
     * image 待模板匹配的原始图,CV_8U/32F
     * templ 模板图像，与原图同类型，尺寸小于原图
     * result 结果输出图像,CV_32F
     * method 模板匹配方法标志
     * mask 掩码，必须与模板图像有相同数据类型和尺寸，默认不设置
     * TM_SQDIFF 平方差匹配法，完全匹配时为0，匹配度越低，计算数值越大
     * TM_SQDIFF_NORMED 归一化平方差匹配法 完全匹配时数值为0，匹配度越低，计算数值越大(0-1)
     * TM_CCOEFF 系数匹配法 对模板减去均值的结果和原始图减去均值的结果匹配，解决由于亮度不同而产生的影响
     *              匹配度越高，数值越大，反之(结果可以为负数)
     * TM_CCOEFF_NORMED 相关系数匹配法
     * */
    Mat mat2 = imread("C:\\Pictures\\16.jpg");
    if (mat2.empty()) {
        cout << "Error" << endl;
        return ;
    }
    resize(mat2,mat2,Size(),0.5,0.5);
    Mat result;
    matchTemplate(mat,mat2,result,TM_CCOEFF_NORMED);//模板匹配
    double maxVal,minVal;
    Point minLoc,maxLoc;
    //寻找匹配结果中最大值和最小值以及坐标位置
    minMaxLoc(result,&minVal,&maxVal,&minLoc,&maxLoc);
    //绘制最佳匹配区域
    rectangle(mat,Rect(maxLoc.x,maxLoc.y,mat2.cols,mat2.rows),Scalar(0,0,255),2);
    imshow("ori img",mat);
    imshow("match template img",mat2);
    imshow("match result",result);
    waitKey(0);
}
